package data

import (
	"context"
	"fmt"
	"log"
	"os"
	"reflect"
	"time"

	"rosesservice/internal/conf"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
)

func getPage(page int64) int {
	if page < 1 || page > 10000 {
		return 1
	}
	return int(page)
}

func getLimit(limit int64) int {
	if limit < 1 || limit > 10000 {
		return 10
	}
	return int(limit)
}

func getOffset(page int, limit int) int {
	return (page - 1) * limit
}

type LimitOffset struct {
	Limit  int
	Offset int
}

// RemoveDuplicates 删除重复的元素
func RemoveDuplicates[T comparable](in []T) []T {
	length := len(in)
	mp := make(map[T]int, length)
	for index, value := range in {
		if _, ok := mp[value]; !ok {
			mp[value] = index
		}
	}
	result := make([]T, 0, length)
	for index, value := range in {
		if mp[value] == index {
			result = append(result, value)
		}
	}
	return result
}

type DB struct {
	db *gorm.DB
}

func NewDB(c *conf.Data) (*DB, error) {
	// driver := "mysql"
	// dsn := "root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
	dsn := fmt.Sprintf("%s?charset=utf8mb4&parseTime=True&loc=Local", c.Database.Source)

	newLog := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold:             time.Second, // Slow SQL threshold
			LogLevel:                  logger.Info, // Log level
			IgnoreRecordNotFoundError: true,        // Ignore ErrRecordNotFound error for logger
			ParameterizedQueries:      true,        // Don't include params in the SQL log
			Colorful:                  false,       // Disable color
		},
	)

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: newLog,
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
		SkipDefaultTransaction: true,
	})
	if err != nil {
		return nil, err
	}

	base, err := db.DB()
	if err != nil {
		return nil, err
	}

	base.SetMaxOpenConns(100)
	base.SetMaxIdleConns(20)
	base.SetConnMaxLifetime(time.Minute * 3)

	// AutoMigrate
	if false {
		err = db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(
			&RbacPermit{},
			&RbacRole{},
			&RbacRolePermit{},
			&RbacRoleUser{},
			&User{},

			&UserIntegral{},
			&UserWithdraw{},

			&UserWechat{},

			&ApproveSources{},
			&UserApproveSources{},
			&SystemNotice{},
			&UserMessage{},

			&SystemConfig{},

			&Article{},
			&ArticleReader{},
			&ArticleRecords{},
			&ArticleRecordsDisposal{},
			&ArticleLedger{},
			&RunValue{},
			&Topic{},
			&Export{},
		)
		if err != nil {
			return nil, err
		}
	}

	return &DB{
		db: db,
	}, nil
}

func (s *DB) DB(values ...*gorm.DB) *gorm.DB {
	for i := len(values) - 1; i >= 0; i-- {
		if values[i] != nil {
			return values[i]
		}
	}
	return s.db
}

func (s *DB) Transaction(tx *gorm.DB, fc func(tx *gorm.DB) error) error {
	db := s.DB(tx)
	if _, ok := db.Get("gorm:tx"); ok {
		return fc(db)
	}
	return db.Transaction(fc)
}

func sqlContext(ctx context.Context) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.WithContext(ctx)
	}
}

func sqlTable(table any) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if value, ok := table.(string); ok {
			return db.Table(value)
		}
		return db.Model(table)
	}
}

func sqlEqual(column string, value any) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.Where(fmt.Sprintf("%s = ?", column), value)
	}
}

func sqlIn(column string, value any) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.Where(fmt.Sprintf("%s IN (?)", column), value)
	}
}

func sqlAsc(columns ...string) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		for _, column := range columns {
			if column != "" {
				db.Order(fmt.Sprintf("%s ASC", column))
			}
		}
		return db
	}
}

func sqlDesc(columns ...string) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		for _, column := range columns {
			if column != "" {
				db.Order(fmt.Sprintf("%s DESC", column))
			}
		}
		return db
	}
}

func sqlLimitOffset(limit int, offset int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if limit <= 0 || offset < 0 {
			return db
		}
		return db.Limit(limit).Offset(offset)
	}
}

// Context Using context.
func (s *DB) Context(ctx context.Context) func(db *gorm.DB) *gorm.DB {
	return sqlContext(ctx)
}

// Delete When deleting data, the WHERE condition should be clearly specified.
func (s *DB) Delete(tx *gorm.DB, table any, where func(db *gorm.DB) *gorm.DB) (int64, error) {
	db := s.DB(tx)
	db = sqlTable(table)(db)
	if where != nil {
		db = db.Scopes(where)
	}
	result := db.Unscoped().Delete(nil)
	return result.RowsAffected, result.Error
}

// Insert You can restrict the set of fields to be inserted through `custom`.
func (s *DB) Insert(tx *gorm.DB, table any, insert any, custom func(db *gorm.DB) *gorm.DB) (int64, error) {
	if insert == nil {
		return 0, nil
	}
	db := s.DB(tx)
	db = sqlTable(table)(db)
	if custom != nil {
		db = db.Scopes(custom)
	}
	result := db.Create(insert)
	return result.RowsAffected, result.Error
}

// Update 构建更新集合对象 updates: []map[string]any OR column1, anyValue1, column2, anyValue2, column3, anyValue3 ... OR [*]AnyStruct
func Update(updates ...any) any {
	length := len(updates)
	if length == 0 {
		return nil
	}
	if length == 1 {
		if values, ok := updates[0].(map[string]any); ok {
			if count := len(values); count > 0 {
				return values
			}
			return nil
		}
		rt := reflect.TypeOf(updates[0])
		kind := rt.Kind()
		if kind == reflect.Ptr {
			rt = rt.Elem()
			kind = rt.Kind()
		}
		if kind == reflect.Struct {
			return updates[0]
		}
		return nil
	}

	modify := make(map[string]any)
	multiMap := true
	for i := 0; i < length; i++ {
		value, ok := updates[i].(map[string]any)
		if !ok {
			multiMap = false
			break
		}
		for k, v := range value {
			modify[k] = v
		}
	}

	// multiple maps
	if multiMap {
		if len(modify) > 0 {
			return modify
		}
		return nil
	}

	// key-value lists
	if (length & 1) != 0 {
		return nil
	}
	modify = make(map[string]any)
	for i := 0; i < length; i += 2 {
		column, ok := updates[i].(string)
		if !ok {
			return nil
		}
		if column == "" {
			continue
		}
		modify[column] = updates[i+1]
	}
	return modify
}

// Update `custom` should include a where condition.
func (s *DB) Update(tx *gorm.DB, table any, custom func(db *gorm.DB) *gorm.DB, update any) (int64, error) {
	if update == nil {
		return 0, nil
	}
	db := s.DB(tx)
	db = sqlTable(table)(db)
	if custom != nil {
		db = db.Scopes(custom)
	}
	result := db.Updates(update)
	return result.RowsAffected, result.Error
}

/* Perform delete and update using the id field value. */

// DeleteById Delete a single record based on id.
func (s *DB) DeleteById(tx *gorm.DB, table any, idValue any) (int64, error) {
	return s.Delete(tx, table, sqlEqual("id", idValue))
}

// DeleteByIds Delete multiple records based on id lists.
func (s *DB) DeleteByIds(tx *gorm.DB, table any, idValues any) (int64, error) {
	return s.Delete(tx, table, sqlIn("id", idValues))
}

// UpdateById Update a single record based on id.
func (s *DB) UpdateById(tx *gorm.DB, table any, idValue any, update any) (int64, error) {
	return s.Update(tx, table, sqlEqual("id", idValue), update)
}

// UpdateByIdsInt64 Update multiple records based on id lists.
func (s *DB) UpdateByIdsInt64(tx *gorm.DB, table any, updates map[int64]any) (int64, error) {
	length := len(updates)
	if length == 0 {
		return 0, nil
	}
	exist := make(map[int64]*struct{}, length)
	lists := make([]int64, 0, length)
	for key := range updates {
		if _, has := exist[key]; has {
			continue
		}
		exist[key] = nil
		lists = append(lists, key)
	}
	rows := int64(0)
	err := s.Transaction(tx, func(tx *gorm.DB) error {
		for _, key := range lists {
			if lines, err := s.UpdateById(tx, table, key, updates[key]); err != nil {
				return err
			} else {
				rows += lines
			}
		}
		return nil
	})
	return rows, err
}

type Table struct {
	table any
	using *gorm.DB
}

// Table 创建一个表实例
func (s *DB) Table(table any, lists ...*gorm.DB) *Table {
	return &Table{
		table: table,
		using: s.DB(lists...),
	}
}

func (s *Table) make() *gorm.DB {
	return sqlTable(s.table)(s.using)
}

// Scopes call (*gorm.DB).Scopes
func (s *Table) Scopes(lists ...func(db *gorm.DB) *gorm.DB) *Table {
	for _, tmp := range lists {
		if tmp != nil {
			s.using = tmp(s.using)
		}
	}
	return s
}

// // Create 向表中插入数据
// func (s *Table) Create(create any) error {
// 	return s.make().Create(create).Error
// }
//
// // Delete 删除表中的数据
// func (s *Table) Delete() error {
// 	return s.make().Unscoped().Delete(nil).Error
// }
//
// // Update 更新表中的数据
// func (s *Table) Update(updates ...any) error {
// 	modify := Update(updates...)
// 	if modify == nil {
// 		return gorm.ErrInvalidData
// 	}
// 	return s.make().Updates(modify).Error
// }

// Count 统计数据总条数
func (s *Table) Count() (total int64, err error) {
	err = s.make().Count(&total).Error
	return total, err
}

// First 查询一条数据
func (s *Table) First(one any) error {
	return s.make().First(one).Error
}

// Find 查询多条数据
func (s *Table) Find(all any) error {
	return s.make().Find(all).Error
}
